技巧2 以守护进程方式运行容器
在熟悉了Docker之后,(与我们一样)读者会开始思考Docker的其他使用场景,首先想到的使用场景之一是以后台服务的方式来运行Docker容器。
以服务方式运行Docker容器,通过软件隔离实现可预测行为,是Docker的主要使用场景之一。本技巧将让开发人员可以使用适合自己的操作的方式来管理服务。
问题
想要以服务方式在后台运行一个Docker容器。
解决方案
在 docker run
命令中使用 -d
标志,并使用相关的容器管理标志定义此服务特性。
Docker容器与多数进程一样,默认在前台运行。在后台运行Docker容器最常见的方式是使用标准的 &
控制操作。虽然这行得通,但如果用户注销终端会话就可能出现问题,用户被迫使用 nohup
标志,而这将在本地目录中创建一个不得不管理的输出文件……是的,使用Docker守护进程的功能完成这一点将简洁得多。
要做到这一点,可使用 -d
标志:
$ docker run -d -i -p 1234:1234 --name daemon ubuntu:14.04 nc -l 1234
与 docker run
一起使用的 -d
标志将以守护进程方式运行容器。 -i
标志则赋予容器与Telnet会话交互的能力。使用 -p
将容器的1234端口公布到宿主机上。通过 --name
标志赋予容器一个名称,以便后期用来对它进行引用。最后,使用netcat( nc
)在1234端口上运行一个简单的监听应答(echo)服务器。
现在可以使用Telnet连接它并发送消息。使用 docker logs
命令可以看到容器已经接收该消息,如代码清单2-1所示。
代码清单2-1 使用Telnet连接容器netcat服务器
$ telnet localhost 1234 ⇽--- 使用telnet命令连接到容器的netcat服务器
Trying ::1...
Connected to localhost.
Escape character is '^]'.
hello daemon ⇽--- 输入发送给netcat服务器的一行文本
^] ⇽--- 按组合键Ctrl+]然后按回车键退出Telnet会话
telnet> q ⇽--- 输入q然后按回车键退出Telnet程序
Connection closed.
$ docker logs daemon ⇽--- 执行docker logs命令查看容器的输出
hello daemon
$ docker rm daemon ⇽--- 使用rm命令清除容器
daemon
$
由此可见,以守护进程方式运行一个容器是相当简单的,但在实际操作中,还有一些问题有待解答。
- 如果服务失败了会发生什么?
- 在服务结束时会发生什么?
- 如果服务不断失败会发生什么?
幸运的是,Docker为每个问题都提供了相应标志!
注意
尽管重启标志经常会与守护进程标志( -d
)一起使用,但与 -d
一起运行这些标志并不是必需的。
docker run --restart
标志允许用户应用一组容器终止时需要遵循的规则(就是所谓的“重启策略”,见表2-1)。
策 略 | 描 述 |
---|---|
no | 容器退出时不重启 |
always | 容器退出时总是自动重启 |
unless-stopped | 总是重启,不过显式停止除外 |
on-failure[:max-retry] | 只在失败时重启 |
no
策略很简单:当容器退出时,它不会被重启。这是默认值。
always
策略也很简单,不过还是值得简要讨论一下:
$ docker run -d --restart=always ubuntu echo done
这个命令以守护进程方式( -d
)运行容器,并总是在容器终止时自动重启( --restart=always
)。它发送了一个简单的快速完成的 echo
命令,然后退出容器。
如果执行了上述命令,然后执行 docker ps
命令,就会看到类似下面这样的输出:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED
➥ STATUS PORTS NAMES
69828b118ec3 ubuntu:14.04 "echo done" 4 seconds ago
➥ Restarting (0) Less than a second ago sick_brattain
dockerps
命令列出了所有运行中的容器及其信息,包括以下内容。
- 容器什么时候被创建的(
CREATED
)。 - 容器的当前状态——通常将是
Restarting
,因为它只运行了很短的时间(STATUS
)。 - 容器上一次运行的退出码(也在
STATUS
下面)。0
代表运行成功。 - 容器名称。默认情况下,Docker会通过连接两个随机单词为容器命名。有时这会造成一些奇怪的结果(这也是我们通常建议给容器起一个有意义的名称的原因)。
注意, STATUS
一栏还告诉我们,容器在不到一秒前退出并正在重启。这是因为 echo done
命令会立即退出,而Docker必须不断地重启这个容器。
需要特别说明的是,Docker复用了容器ID。这个ID在重启时不会改变,并且对于这个Docker调用来说, ps
表里永远只会有一条。
指定 unless-stopped
与 always
几乎是一样的——二者都将在你执行 docker stop
时停止容器重启,不过 unless-stopped
将确保在守护进程重启时记住其停止状态(可能是你重启了计算机),而 always
则会再次启动容器。
最后, on-failure
策略只在容器从它的主进程返回一个非0退出码(一般表示失败)时重启:
$ docker run -d --restart=on-failure:10 ubuntu /bin/false
这条命令以守护进程( -d
)形式运行容器,并设置了重启的尝试次数限制( --restart= on-failure:10
),如果超出限制则退出。它执行了一个快速完成并肯定失败的简单的命令( /bin/false
)。
如果执行上述命令并等待1分钟,然后执行 docker ps -a
,就会看到类似下面这样的输出:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED
➥ STATUS PORTS NAMES
b0f40c410fe3 ubuntu:14.04 "/bin/false" 2 minutes ago
➥ Exited (1) 25 seconds ago loving_rosalind
讨论
创建后台运行的服务经常碰到的困难在于确保后台服务在非常规环境中不会崩溃。因为这不是立马可见的,用户可能不会意识到有东西无法正常工作了。
本技巧让你无须再考虑由环境和处理重启引起的服务的偶然复杂度。你可以将思想集中在核心功能上。
举个具体的例子,你和团队可能会使用这个技巧在同一台机器上运行多个数据库而无须编写设置这些数据库的说明,也不用打开终端以保持其运行。